package chatClient.controller;

import java.io.*;
import java.net.*;
import java.rmi.ConnectException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.util.*;

import chat.model.*;
import chatClient.view.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ChatClientConnection implements ObserverChatSender, ObservableChatConnection
{
	private String serverFile;
	private ArrayList <InetAddress> serverList;
	private InetAddress activeServer;
	private Socket socket = null;
	private ChatListener chatListener;
	private ChatSender chatSender;
	private boolean hasActiveConnection;
	private boolean wantConnectionWithServer;
	private List <ObserverChatConnection> observerConnection;
	private ChatServerRmi chatServer;
	private ScheduledThreadPoolExecutor heartBeatingServerTimer;
	private boolean isHeartBeepingServer;
	
	//create a TimerTask which will periodically beeps the server to verify if we are having with the server.
	HeartBeatingServer heartBeatingTheServer;
	private class HeartBeatingServer implements Runnable
	{
		public void run()
		{
			if (wantConnectionWithServer)
			{
				//do an RMI call to the server to make sure it is still alive
				try
				{
					boolean isActivate = chatServer.isActive();
					
					notifyObserverChatConnection_ConnectionStatus(ApplicationConstant.CONNECTED);
				}
				catch(Exception ex)
				{
					//we could not connect to the server but we want a connection with server, 
					//so we need to re-connect immediately
					System.out.println("HeartBeepingTheServer: Could not connect the server - Reconnecting");
					
					//We also send this status to all people who are observing this connection
					//such as the GUI or processes.
					notifyObserverChatConnection_ConnectionStatus(ApplicationConstant.DISCONNECTED);
					
					//reconnecting
					//what happen if we are still re-connecting
					//this method is call the second time while the previous call have not finished
					createConnection();
				}
			}
			else
			{
				isHeartBeepingServer = false;
				//if user does not want connection with the server then don't need to do heartBeeping any more.
				//should we have some delay? http://stackoverflow.com/questions/6774816/scheduledthreadpoolexecutor-how-stop-runnable-class-java
				heartBeatingServerTimer.shutdownNow();
			}
		}
	}
	
	public ChatClientConnection()
	{
		observerConnection = new ArrayList <ObserverChatConnection>();
		observerConnection.clear();
		wantConnectionWithServer = true;
		isHeartBeepingServer = false;
		heartBeatingTheServer = new HeartBeatingServer();
		
		try
		{
//			observerList= new ArrayList <ObserverChat>();
			serverList = new ArrayList<InetAddress>();
			chatListener = new ChatListener();
			chatSender = new ChatSender();
			
			hasActiveConnection = false;
				
		}
		catch(Exception ex)
		{
			System.out.println("\nError when allocating memory for variables!\n");
			System.exit(1);
		}
	}
	
	//read all the servers from serverFile
	//suppose that all servers listen on the same port
	public void ReadServerFile(String theServerFile)
	{
		serverFile = theServerFile;
		try
		{
			//ref: http://www.roseindia.net/java/beginners/java-read-file-line-by-line.shtml
			FileReader fr = new FileReader(serverFile);
			BufferedReader br = new BufferedReader( fr);
			String line;
			
			while( (line = br.readLine()) != null)
			{
				InetAddress add = InetAddress.getByName(line);
				serverList.add(add);				
			}
			
			//close the input stream
			fr.close();
		}
		catch(Exception ex)
		{
			System.out.println("Error when reading the host file");
			System.exit(1);
		}
	}
	
	//try to connect to one of the server
	public boolean createConnection()
	{
		System.out.println("Creating connection");
		for(InetAddress host: serverList)
		{
			if(connectToServer(host))
			{
				//the first server that client can connect become the active server for the client
				activeServer = host;
				return true;
			}
		}
		
		return false;
	}
	
	//close the connection
	public void closeConnection()
	{
		try
		{
			System.out.println("\nClosing connection...");
			chatListener.setStop(true);
			
			socket.close();
			hasActiveConnection = false;
		}
		catch(IOException ioEx)
		{
			System.out.println("Unable to disconnect!");
			System.exit(1);
		}
	}	
	
	//create a connection to a server
	private boolean connectToServer(InetAddress host)
	{
		//do a RMI call to the server, if the server is active then we will connect to the server, 
		//otherwise, we will just simply exit and return false.
		try
		{
			
			String rmiAddress = "rmi://" + host.getHostAddress() + "/ChatServerRmi";
//			chatServer = (ChatServerRmi)Naming.lookup("rmi://" + host + "/ChatServerRmi");
			chatServer = (ChatServerRmi)Naming.lookup(rmiAddress);
			
			boolean isActive = chatServer.isActive();
			
			//only connect to active server. It means even we can do a RMI call to a server, we may not
			// connect to if it is not an active server.
			if (isActive)
			{
				System.out.println("The master server is activated " + host.getHostAddress());
				socket = new Socket(host,ApplicationConstant.SERVER_PORT);
				chatListener.createInputPipe(socket.getInputStream());
				chatSender.createOutputPipe(socket.getOutputStream());
				
				//starts the listener thread
				Thread t = new Thread(chatListener);
				t.start();
				hasActiveConnection = true;
				
				//periodically beeping the sever
				//time is in millisecond.
				//we may have already started the heartBeating timmer so we don't want to start a second time.
				if (!isHeartBeepingServer)
				{
					heartBeatingServerTimer = new ScheduledThreadPoolExecutor(1);
					heartBeatingServerTimer.scheduleAtFixedRate(heartBeatingTheServer, 0, ApplicationConstant.BEATING_RATE, TimeUnit.MICROSECONDS);
				}
				return true;
			}
			else
			{
				System.out.println("Trying to connect to server: " + host.getHostAddress() + " but this server is not active");
				return false;
			}
		}
		//if has exception so it means that this server is probably down.
		catch(IOException ioEx)
		{
			//can't connect to the server
			ioEx.printStackTrace();
			return false;
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
			System.out.println("Trying to connect to server: " + host.getHostAddress() + " but this server seems to be down");
			return false;
		}
	}
	
	//add observers to the listener so whenever a new message comes, all observers will be notified.
	public void addObserverChatListener(ObserverChatListener observer){
		chatListener.addObserverChatListener(observer);
	}
	
	public void sendMessage(String message){
		if (hasActiveConnection)
		{
			System.out.println("Sending message");
			chatSender.sendMessage(message);
		}
	}
		
	public void connectToServer()
	{
		wantConnectionWithServer = true;
		if (!hasActiveConnection)
		{
			hasActiveConnection = createConnection();
		}
	}
	
	public void disconnectingServer()
	{
		wantConnectionWithServer = false;
		closeConnection();
	}
	
	public void notifyObserverChatConnection_ConnectionStatus(String status)
	{
		for (ObserverChatConnection connection:observerConnection)
		{
			connection.updateConnectionStatus(status);
		}
	}
	
	public void addObserverChatConnection(ObserverChatConnection observer)
	{
		observerConnection.add(observer);
	}
}